//==============================================================================
//
//             DColor - the color class in the ofc-library
//
//               Copyright (C) 2003  Dick van Oudheusden
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//==============================================================================
// 
//  $Date: 2005-11-12 10:04:03 $ $Revision: 1.7 $
//
//==============================================================================

#include "ofc/DColor.h"

#include <math.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>

#if _INTERFACE_

#include "ofc/config.h"

#include "ofc/DTextable.h"
#include "ofc/DParsable.h"

#include "ofc/DText.h"

#define DCLR_UNKNOWN         (-1)   // the unknown text color
#define DCLR_BLACK           (0)    // the black text color
#define DCLR_RED             (1)    // the red text color
#define DCLR_GREEN           (2)    // the green text color
#define DCLR_YELLOW          (3)    // the yellow text color
#define DCLR_BLUE            (4)    // the blue text color
#define DCLR_MAGENTA         (5)    // the magenta text color
#define DCLR_CYAN            (6)    // the cyan text color
#define DCLR_WHITE           (7)    // the white text color

//
// The color class implements a number of methods for using colors.
//

@interface DColor : Object <DTextable,DParsable>
{
@public
  unsigned char _red;         // the red color 255 = full
  unsigned char _green;       // the green color 255 = full
  unsigned char _blue;        // the blue color 255 = full
  unsigned char _alpha;       // the alpha factor 255 = solid
  int           _text;        // the (alternative) text color (DCLR_... or -1)
}

#endif



@implementation DColor

#if _PRIVATE_

typedef struct
{
  const char     *name;
  BOOL            isTextColor;
  int             textColor;
  unsigned char   red;
  unsigned char   green;
  unsigned char   blue;
} _Color;


static _Color _colors[] =
{
  { "aqua",    NO,  DCLR_BLUE,    102, 205, 170 },
  { "black",   YES, DCLR_BLACK,     0,   0,   0 },
  { "blue",    YES, DCLR_BLUE,      0,   0, 255 },
  { "cyan",    YES, DCLR_CYAN,      0, 255, 255 },
  { "maroon",  NO,  DCLR_BLUE,    176,  48,  96 },
  { "magenta", YES, DCLR_MAGENTA, 255,   0, 255 },
  { "fuchsia", NO,  DCLR_MAGENTA, 255,   0, 255 },
  { "navy",    NO,  DCLR_BLUE,      0,   0, 128 },
  { "olive",   NO,  DCLR_BLUE,     85, 107,  47 },
  { "purple",  NO,  DCLR_MAGENTA, 160,  32, 240 },
  { "red",     YES, DCLR_RED,     255,   0,   0 },
  { "gray",    NO,  DCLR_BLACK,   190, 190, 190 },
  { "green",   YES, DCLR_GREEN,     0, 255,   0 },
  { "silver",  NO,  DCLR_BLACK,   192, 192, 192 },
  { "teal",    NO,  DCLR_CYAN,      0, 128, 128 },
  { "lime",    NO,  DCLR_GREEN,    50, 205,  50 },
  { "white",   YES, DCLR_WHITE,   255, 255, 255 },
  { "yellow",  YES, DCLR_YELLOW,  255, 255,   0 }
};

#endif


//// Constructors

//
// Initialise an empty (black) color
//
// @return the object
//

- (DColor *) init
{
  [super init];

  _red   = 0;
  _blue  = 0;
  _green = 0;
  _alpha = 255;
  _text  = DCLR_BLACK;
  
  return self;
}

//
// Initialise to a solid named color
//
// @param name     the color name
//
// @return the object
//

- (DColor *) init :(const char *) name
{
  [self init];

  [self set :name];

  return self;
}

//
// Initialise to a solid rgb color
//
// @param red      the red factor (0..255)
// @param blue     the blue factor (0..255)
// @param green    the green factor (0..255)
//
// @return the object
//

- (DColor *) init :(unsigned char) red :(unsigned char) blue :(unsigned char) green
{
  [self init];

  [self set :red :blue :green];

  return self;
}

//
// Initialise to a transparent rgb color
//
// @param red      the red factor (0..255)
// @param blue     the blue factor (0..255)
// @param green    the green factor (0..255)
// @param alpha    the alpha factor (0..255)
//
// @return the object
//

- (DColor *) init :(unsigned char) red :(unsigned char) blue :(unsigned char) green :(unsigned char) alpha
{
  [self init];

  [self set :red :blue :green :alpha];

  return self;
}


//// Member methods

//
// Get the red factor
//
// @return the red factor
//

- (unsigned char) red
{
  return _red;
}

//
// Get the green factor
//
// @return the green factor
//

- (unsigned char) green
{
  return _green;
}

//
// Get the blue factor
//
// @return the blue factor
//

- (unsigned char) blue
{
  return _blue;
}

//
// Get the alpha factor
//
// @return the solid factor
//

- (unsigned char) alpha
{
  return _alpha;
}

//
// Set the alpha factor
// 
// @param alpha    the transparent factor (0..255)
// 
// @return the object
// 

- (DColor *) alpha :(unsigned char) alpha
{
  _alpha = alpha;
  
  return self;
}

//
// Get the text color
//
// @return the text color
//

- (int) textColor
{
  return _text;
}

//
// Set the text color
// 
// @param color    the text color (DCLR_..)
// 
// @return the object
// 

- (DColor *) textColor :(int) color
{
  if ((color < DCLR_BLACK) || (color > DCLR_WHITE))
  {
    WARNING(DW_INVALID_ARG, "color");
  }
  else
  {
    _text = color;
  }
  
  return self;
}


//// Set the color

//
// Set a color based on a name
// (alpha is not changed)
// 
// @param name     the name of the color
// 
// @return is the name found ?
// 

- (BOOL) set :(const char *) name
{
  BOOL ok = NO;
  
  if ((name == NULL) || (*name == EOS))
  {
    WARNING(DW_INVALID_ARG, "name");
  }
  else
  {
    int color  = 0;
    int number = sizeof(_colors) / sizeof(_Color);
    
    while ((!ok) && (color < number))
    {
      if (strcasecmp(name, _colors[color].name) == 0)
      {
        _red   = _colors[color].red;
        _green = _colors[color].green;
        _blue  = _colors[color].blue;
        _text  = _colors[color].textColor;
        
        ok = YES;
      }
      color++;
    }
  }

  return ok;
}

//
// Set a color based on rgb 
// (alpha and text color are not changed)
// 
// @param red      the red color (0..255)
// @param green    the green color (0..255)
// @param blue     the blue color (0..255)
// 
// @return the object
// 

- (DColor *) set :(unsigned char) red :(unsigned char) green :(unsigned char) blue
{
  _red   = red;
  _green = green;
  _blue  = blue;
  
  return self;
}

//
// Set a transparent color based on rgb
// (text color is not changed)
// 
// @param red      the red color (0..255)
// @param green    the green color (0..255)
// @param blue     the blue color (0..255)
// @param alpha    the transparent factor (0..255)
//
// @return the object
//  

- (DColor *) set :(unsigned char) red :(unsigned char) green :(unsigned char) blue :(unsigned char) alpha
{
  [self set :red :green :blue];
  
  [self alpha :alpha];
  
  return self;
}

//
// Set a transparent color based on rgb and a text color
// 
// @param red      the red color (0..255)
// @param green    the green color (0..255)
// @param blue     the blue color (0..255)
// @param alpha    the transparent factor (0..255)
// @param color    the text color (DCLR_..)
//
// @return the object
// 

- (DColor *) set :(unsigned char) red :(unsigned char) green :(unsigned char) blue :(unsigned char) alpha :(int) color
{
  [self set :red :green :blue];
  
  [self alpha :alpha];
  
  [self textColor :color];
  
  return self;
}


//// Color manipulation

//
// Make the color lighter or darker
// 
// @param factor   the lighter (<1.0..>) or darker [0.0..1.0>) factor
//
// @return the object
// 

- (DColor *) lighter :(double) factor
{
  if (factor < 0.0)
  {
    WARNING(DW_INVALID_ARG, "factor");
  }
  else
  {
    double h,s,v;
  
    [self toHSV :&h :&s :&v];
  
    v *= factor;
    
    if (v < 0.0)
      v = 0.0;
    if (v > 1.0)
      v = 1.0;
    
    [self fromHSV :h :s :v];
  }
  
  return self;
} 

//
// Alpha blending with a color
// 
// @param red      the red factor of the color (0..255)
// @param green    the green factor of the color (0..255)
// @param blue     the blue factor of the color (0..255)
// @param alpha    the alpha blending factor (0..255)
// 
// @return the object
// 

- (DColor *) blend :(unsigned char) red :(unsigned char) green :(unsigned char) blue :(unsigned char) alpha
{
  _red   = _red   + (red   - _red  ) * alpha;
  _green = _green + (green - _green) * alpha;
  _blue  = _blue  + (blue  - _blue ) * alpha;
  
  return self;
}


//// Color systems

//
// 'Convert' to RGB
// 
// @param red      the destination for red (0.0 .. 1.0)
// @param green    the destination for green (0.0 .. 1.0)
// @param blue     the destination for blue (0.0 .. 1.0)
// 
// @return the object
// 

- (DColor *) toRGB :(double *) red :(double *) green :(double *) blue
{
  *red   = (double) _red   / 255.0;
  *green = (double) _green / 255.0;
  *blue  = (double) _blue  / 255.0;
  
  return self;
}

//
// 'Convert' from RGB
// 
// @param red      the red color (0.0 .. 1.0)
// @param green    the green color (0.0 .. 1.0)
// @param blue     the blue color (0.0 .. 1.0)
// 
// @return the object
// 

- (DColor *) fromRGB :(double) red :(double) green :(double) blue
{
  [self set :(unsigned char)(red * 255.0 + 0.5) :(unsigned char)(green * 255.0 + 0.5) :(unsigned char)(blue * 255.0 + 0.5)];
  
  return self;
}

//
// Convert to the composite video signals YIQ
// (Y = grey level, I+Q color components)
// 
// @param Y        the destination for Y (0.0 .. 1.0)
// @param I        the destination for I (0.0 .. 1.0)
// @param Q        the destination for Q (0.0 .. 1.0)
// 
// @return the object
// 

- (DColor *) toYIQ :(double *) Y :(double *) I :(double *) Q
{
  double red   = (double) _red   / 255.0;
  double green = (double) _green / 255.0;
  double blue  = (double) _blue  / 255.0;
  
  *Y = (0.299 * red + 0.587 * green + 0.114 * blue);
  *I = (0.596 * red - 0.275 * green - 0.321 * blue);
  *Q = (0.212 * red - 0.523 * green + 0.311 * blue);
  
  return self;
}

//
// Convert from YIQ
// 
// @param Y        the Y (0.0 .. 1.0)
// @param I        the I (0.0 .. 1.0)
// @param Q        the Q (0.0 .. 1.0)
// 
// @return the object
// 

- (DColor *) fromYIQ :(double) Y :(double) I :(double) Q
{
  double red   = Y + 0.948262 * I + 0.624013 * Q;
  double green = Y - 0.276066 * I - 0.639810 * Q;
  double blue  = Y - 1.105450 * I + 1.729860 * Q;
  
  if (red   < 0.0) red   = 0.0;
  if (red   > 1.0) red   = 1.0;
  if (green < 0.0) green = 0.0;
  if (green > 1.0) green = 1.0;
  if (blue  < 0.0) blue  = 0.0;
  if (blue  > 1.0) blue  = 1.0;
  
  [self fromRGB :red :green :blue];
  
  return self;
}

//
// Convert to HLS
// (H = Hue, L=Lightness, S=Saturation)
// 
// @param H        the destination for H (0.0 .. 359.9)
// @param L        the destination for L (0.0 .. 1.0)
// @param S        the destination for S (0.0 .. 1.0)
// 
// @return the object
// 

- (DColor *) toHLS :(double *) H :(double *) L :(double *) S
{
  if ((H == NULL) || (L == NULL) || (S == NULL))
  {
    WARNING(DW_INVALID_ARG, "H,L,S");
  }
  else
  {
    double red   = (double) _red   / 255.0;
    double green = (double) _green / 255.0;
    double blue  = (double) _blue  / 255.0;
    
    double high,low,sum,diff;
  
    // calculate the high, low, sum and diff from the rgb colors
    if (red > green)
    {
      high = red;
      low  = green;
    }
    else
    {
      high = green;
      low  = red;
    }
  
    if (blue > high)
    {
      high = blue;
    }
    else if (blue < low)
    {
      low = blue;
    }
    
    sum  = high + low;
    diff = high - low;

    *L = 0.5 * sum;
    
    if (high == low)
    {
      *H = 0.0;
      *S = 0.0;
    }
    else
    {
      double fr,fg,fb;
    
      fr = (high - red  ) / diff;
      fg = (high - green) / diff;
      fb = (high - blue ) / diff;
      
      if (*L < 0.5)
      {
        *S = diff / sum;
      }
      else
      {
        *S = diff / (2.0 - sum);
      }
    
      if (red == high)
      {
        *H = 60.0 * (6.0 + fb - fg);
      }
      else if (green == high)
      {
        *H = 60.0 * (2.0 + fr - fb);
      }
      else
      {
        *H = 60.0 * (4.0 + fg - fr);
      }
    
      if (*H >= 360.0)
      {
        *H = *H - 360.0;
      }
    }
  }
  
  return self;
}

#if _PRIVATE_

static double pHLS(double m1, double m2, double hue)
{
  double p;
  
  if (hue > 360.0)
    hue -= 360.0;
  
  if (hue < 0.0)
    hue += 360.0;
  
  if (hue < 60.0)
  {
    p = m1 + (m2 - m1) * hue / 60.0;
  }
  else if (hue < 180.0)
  {
    p = m2;
  }
  else if (hue < 240.0)
  {
    p = m1 + (m2 - m1) * (240.0 - hue) / 60.0;
  }
  else
  {
    p = m1;
  }
  
  return p;
}
#endif

//
// Convert from HLS
// 
// @param H        the H (0.0 .. 359.9)
// @param L        the L (0.0 .. 1.0)
// @param S        the S (0.0 .. 1.0)
// 
// @return the object
// 

- (DColor *) fromHLS :(double) H :(double) L :(double) S
{
  if ((H < 0.0) || (H >= 360.0))
  {
    WARNING(DW_INVALID_ARG, "H");
  }
  else if ((L < 0.0) || (L > 1.0))
  {
    WARNING(DW_INVALID_ARG, "L");
  }
  else if ((S < 0.0) || (S > 1.0))
  {
    WARNING(DW_INVALID_ARG, "S");
  }
  else
  {
    if (S == 0.0)
    {
      [self fromRGB :L :L :L];
    }
    else
    {
      double m1,m2;
    
      if (L <= 0.5)
      {
        m2 = L * (1.0 + S);
      }
      else
      {
        m2 = L + S - (L * S);
      }
      m1 = 2.0 * L - m2;
    
      [self fromRGB :pHLS(m1,m2,H+120) :pHLS(m1,m2,H) :pHLS(m1,m2,H-120)];
    }
  }
  
  return self;
}

//
// Convert to HSV
// (H = Hue, S=Saturation, Value)
// 
// @param H        the destination for H (0.0 .. 359.9)
// @param S        the destination for S (0.0 .. 1.0)
// @param V        the destination for V (0.0 .. 1.0)
// 
// @return the object
// 

- (DColor *) toHSV :(double *) H :(double *) S :(double *) V
{
  if ((H == NULL) || (S == NULL) || (V == NULL))
  {
    WARNING(DW_INVALID_ARG, "H,S,V");
  }
  else
  {
    double red   = (double) _red   / 255.0;
    double green = (double) _green / 255.0;
    double blue  = (double) _blue  / 255.0;
    
    double high,low,sum,diff;
  
    // calculate the high, low, sum and diff from the rgb colors
    if (red > green)
    {
      high = red;
      low  = green;
    }
    else
    {
      high = green;
      low  = red;
    }
  
    if (blue > high)
    {
      high = blue;
    }
    else if (blue < low)
    {
      low = blue;
    }
    
    sum  = high + low;
    diff = high - low;

    *V = high;
    
    if (high == low)
    {
      *H = 0.0;
      *S = 0.0;
    }
    else
    {
      double fr,fg,fb;

      *S = diff / high;
      
      fr = (high - red  ) / diff;
      fg = (high - green) / diff;
      fb = (high - blue ) / diff;
      
      if (red == high)
      {
        *H = 60.0 * (fb - fg);
      }
      else if (green == high)
      {
        *H = 60.0 * (2.0 + fr - fb);
      }
      else
      {
        *H = 60.0 * (4.0 + fg - fr);
      }
    
      if (*H >= 360.0)
      {
        *H = *H - 360.0;
      }
      if (*H < 0.0)
      {
        *H = *H + 360.0;
      }
    }
  }
  
  return self;
}

//
// Convert from HSV
// 
// @param H        the H (0.0 .. 359.9)
// @param S        the S (0.0 .. 1.0)
// @param V        the V (0.0 .. 1.0)
// 
// @return the object
// 

- (DColor *) fromHSV :(double) H :(double) S :(double) V
{
  if ((H < 0.0) || (H >= 360.0))
  {
    WARNING(DW_INVALID_ARG, "H");
  }
  else if ((S < 0.0) || (S > 1.0))
  {
    WARNING(DW_INVALID_ARG, "S");
  }
  else if ((V < 0.0) || (V > 1.0))
  {
    WARNING(DW_INVALID_ARG, "V");
  }
  else
  {
    if (S == 0.0)
    {
      [self fromRGB :V :V :V];
    }
    else
    {
      double f,p,q,t;
      double h2;
      int    i;
    
      h2 = H / 60.0;
      i  = (int) floor(h2);
      f  = h2 - i;
      p  = V * (1.0 - S);
      q  = V * (1.0 - S * f);
      t  = V * (1.0 - S * (1.0 - f));
      
      switch(i)
      {
       case 0 : 
       case 6 : [self fromRGB :V :t :p]; break;
       case 1 : [self fromRGB :q :V :p]; break;
       case 2 : [self fromRGB :p :V :t]; break;
       case 3 : [self fromRGB :p :q :V]; break;
       case 4 : [self fromRGB :t :p :V]; break;
       case 5 : [self fromRGB :V :p :q]; break;
      }
    }
  }
  
  return self;
}

//
// Convert to CMY
// (C = Cyan, M = Magenta, Y = Yellow)
// 
// @param C        the destination for C (0.0 .. 1.0)
// @param M        the destination for M (0.0 .. 1.0)
// @param Y        the destination for Y (0.0 .. 1.0)
// 
// @return the object
// 

- (DColor *) toCMY :(double *) C :(double *) M :(double *) Y
{
  if ((C == NULL) || (M == NULL) || (Y == NULL))
  {
    WARNING(DW_INVALID_ARG, "C,M,Y");
  }
  else
  {
    *C = 1.0 - ((double) _red   / 255.0);
    *M = 1.0 - ((double) _green / 255.0);
    *Y = 1.0 - ((double) _blue  / 255.0);
  }
  
  return self;
}

//
// Convert from CMY
// 
// @param C        the C (0.0 .. 1.0)
// @param M        the M (0.0 .. 1.0)
// @param Y        the Y (0.0 .. 1.0)
// 
// @return the object
// 

- (DColor *) fromCMY :(double) C :(double) M :(double) Y
{
  if ((C < 0.0) || (C > 1.0))
  {
    WARNING(DW_INVALID_ARG, "C");
  }
  else if ((M < 0.0) || (M > 1.0))
  {
    WARNING(DW_INVALID_ARG, "M");
  }
  else if ((Y < 0.0) || (Y > 1.0))
  {
    WARNING(DW_INVALID_ARG, "Y");
  }
  else
  {
    [self fromRGB :(1.0 - C) :(1.0 - M) :(1.0 - Y)];
  }
  
  return self;
}


//// Textable protocol methods

//
// Convert to a text string (RGB coded in six hex digits, 
// if a text color is known, it is added to the six hex
// digits)
//
// @return a (new) text string with the color
//

- (DText *) toText
{
  DText *str = [DText new];
  
  [str format :"%02X%02X%02X", _red, _green, _blue];
  
  if (_text != DCLR_UNKNOWN)
  {
    int color  = 0;
    int number = sizeof(_colors) / sizeof(_Color);

    while (color < number)
    {
      if ((_colors[color].isTextColor == YES) &&
          (_colors[color].textColor   == _text))
      {
        [str push   :','];
        [str append :_colors[color].name];
        
        break;
      }
      
      color++;
    }
  }

  return str;
}


//// Parsable protocol members

#if _PRIVATE_

static unsigned char fromHex1(char **cstr)
{
  char         *pntr = *cstr;
  char          ch   = tolower(*pntr);
  unsigned char conv = 0;
  
  if ((ch >= 'a') && (ch <= 'f'))
  {
    conv = (unsigned char)(ch - 'a' + 10);
  }
  else if ((ch >= '0') && (ch <= '9'))
  {
    conv = (unsigned char)(ch - '0');
  }
  conv = conv * 16 + conv;

  *cstr = pntr+1;
  
  return conv;
}

static unsigned char fromHex2(char **cstr)
{
  char         *pntr = *cstr;
  char          ch   = tolower(*pntr);
  unsigned char conv = 0;
  
  if ((ch >= 'a') && (ch <= 'f'))
  {
    conv = (unsigned char)(16 * (ch - 'a' + 10));
  }
  else if ((ch >= '0') && (ch <= '9'))
  {
    conv = (unsigned char)(16 * (ch - '0'));
  }
  
  pntr++;
  
  ch = tolower(*pntr);
  
  if ((ch >= 'a') && (ch <= 'f'))
  {
    conv = (unsigned char)(ch - 'a' + 10) + conv;
  }
  else if ((ch >= '0') && (ch <= '9'))
  {
    conv = (unsigned char)(ch - '0') + conv;
  }
  
  pntr++;
  
  *cstr = pntr;

  return conv;
}

static unsigned char fromDec(char **cstr, unsigned char *value)
{
  int   result = 0;
  char *pntr   = *cstr;
  char *scan   = *cstr;
  
  while (isdigit(*scan))
  {
    scan++;
  }
  
  if (*scan == '.')
  {
    double conv = strtod(pntr, cstr);
    
    if ((conv >= 0.0) && (conv <= 1.0))
    {
      *value = (unsigned char)(conv * 255.0 + 0.5);
    }
    else
    {
      result = ERANGE;
    }
  }
  else if (*scan == '%')
  {
    long conv = strtol(pntr, cstr, 10);
    
    if ((conv >= 0) && (conv <= 100))
    {
      *value = (unsigned char)(((double) conv) * 255.0 / 100.0 + 0.5);
      
      (*cstr)++;
    }
    else
    {
      result = ERANGE;
    }
  }
  else
  {
    long conv = strtol(pntr, cstr, 10);
    
    if ((conv >= 0) && (conv <= 255))
    {
      *value = (unsigned char) conv;
    }
    else
    {
      result = ERANGE;
    }
  }
  
  return result;
}

#endif

//
// Parse a string to set a color object.
// This method supports the following formats: <br>
//  #RGB        -- color is defined by three hex digits <br>
//  #RRGGBB     -- color is defined by six hex digits <br>
//  R%,G%,B%    -- color is defined by three percentage digits <br>
//  R.R,G.G,B.B -- color is defined by three floats (0.0 .. 1.0) <br>
//  R,G,B       -- color is defined by three bytes (0 .. 255) <br>
//  name        -- color is defined by an internal name <br>
// the color can be followed by a comma and a text color name <br>
// Example: #8042AF,black <br>
//
// @param cstr     the string to be parsed (moved to first non-parsable char)
// 
// @return the result (0, ERANGE, ENODATA)
//

- (int) fromString :(char **) cstr
{
  int   result = ENODATA;
  char *pntr   = *cstr;
  char *origin = pntr;
  
  while (isspace(*pntr))
  {
    pntr++;
  }
  
  if (*pntr == '#')
  {
    int cnt = 0;
    
    pntr++;
    
    origin = pntr;
    
    while (isxdigit(*pntr))
    {
      pntr++;
      cnt++;
    }
    
    pntr = origin;
    
    if (cnt == 3)
    {
      _red   = fromHex1(&pntr);
      _green = fromHex1(&pntr);
      _blue  = fromHex1(&pntr);
      
      result = 0;
    }
    else if (cnt == 6)
    {
      _red   = fromHex2(&pntr);
      _green = fromHex2(&pntr);
      _blue  = fromHex2(&pntr);
      
      result = 0;
    }
    else
    {
      result = ERANGE;
    }
  }
  else if (isdigit(*pntr))
  {
    unsigned char red,green,blue;
    
    result = fromDec(&pntr, &red);
    
    if ((result == 0) && (*pntr == ','))
    {
      pntr++;
      
      result = fromDec(&pntr, &green);
    }
   
    if ((result == 0) && (*pntr == ','))
    {
      pntr++;
      
      result = fromDec(&pntr, &blue);
    }
    else
    {
      result = ERANGE;
    }
    
    if (result == 0)
    {
      _red   = red;
      _green = green;
      _blue  = blue;
    }
  }
  else
  {
    int color  = 0;
    int number = sizeof(_colors) / sizeof(_Color);

    result = ENODATA;
    
    while (color < number)
    {
      int length = strlen(_colors[color].name);
      
      if (strncasecmp(pntr, _colors[color].name, length) == 0)
      {
        _red   = _colors[color].red;
        _green = _colors[color].green;
        _blue  = _colors[color].blue;
        _text  = _colors[color].textColor;
        
        pntr += length;
        
        result = 0;
        
        break;
      }
      color++;
    }
  }
  
  if ((result == 0) && (*pntr == ','))
  {
    // scan for alternative text color
    int color  = 0;
    int number = sizeof(_colors) / sizeof(_Color);
    
    *cstr = pntr;

    pntr++;
    
    while (color < number)
    {
      int length = strlen(_colors[color].name);
      
      if ((_colors[color].isTextColor) &&
          (strncasecmp(pntr, _colors[color].name, length) == 0))
      {
        _text = _colors[color].textColor;
        
        pntr += length;
        
        break;
      }
      
      color++;
    }
  }

  if (result == 0)
  {
    *cstr = pntr;
  }
  
  return result;
}

@end

/*===========================================================================*/

